/*
 * Copyright (C) 2012  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.soap.axis.client;

import java.net.URL;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import org.apache.axis.client.Stub;
import ca.gc.dfo.wds.BoundaryDate;
import ca.gc.dfo.wds.BoundaryDepth;
import ca.gc.dfo.wds.BoundarySpatial;
import ca.gc.dfo.wds.Metadata;
import ca.gc.dfo.wds.ResultSet;
import ca.gc.dfo.wds.Status;
import ca.gc.shc.ws.ResultSpine;
import ca.gc.shc.ws.SpineServiceLocator;
import ca.gc.shc.ws.SpineSoapBindingStub;

import java.net.MalformedURLException;
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import org.apache.axis.AxisFault;

/**
 * Class {@code SpineClient} eases the communication with the SPINE web service
 * of <a href="http://www.dfo-mpo.gc.ca/">Fisheries and Oceans Canada</a>.
 * <p>
 * The default URL of the web service is
 * <a href="https://ws-shc.qc.dfo-mpo.gc.ca/spine">https://ws-shc.qc.dfo-mpo.gc.ca/spine</a>.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.soap.axis.client.SpineClient">Daniel Léonard</a>
 */
public class SpineClient extends WDSClient {

//---------------------------
// enums
//---------------------------

   /**
    * Enum {@code DataType} describes the only possible data type that can be
    * queried from the SPINE service.
    */
   public enum DataType {
      /** Data type for water level forecasts. */
      spine,
      }


//---------------------------
// Class variables
//---------------------------

   /** The default URL for the SPINE service. */
   public static final String DEFAULT_URL = "https://ws-shc.qc.dfo-mpo.gc.ca/spine"; //$NON-NLS-1$


//---------------------------
// Instance variables
//---------------------------

   /** The auto-generated bindings. */
   private SpineSoapBindingStub bindings;


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    * @param   username   the username.
    * @param   password   the password.
    * @exception    AxisFault   if something goes wrong with Axis.
    * @exception    MalformedURLException   if something goes wrong with the
    *                                       default URL.
    * @exception   ServiceException   if something goes wrong with Axis.
    * @see   #DEFAULT_URL
    */
   public SpineClient(String username, String password) throws AxisFault, MalformedURLException, ServiceException {
      this(DEFAULT_URL, username, password);
      }

   /**
    * Constructor.
    * @param   url   the web service URL.
    * @param   username   the username.
    * @param   password   the password.
    * @exception    AxisFault   if something goes wrong with Axis.
    * @exception    MalformedURLException   if something goes wrong with the.
    *                                       url.
    * @exception   ServiceException   if something goes wrong with Axis.
    */
   public SpineClient(String url, String username, String password) throws AxisFault, MalformedURLException, ServiceException {
      this(new URL(url), username, password);
      }

   /**
    * Constructor.
    * @param   url   the web service URL.
    * @param   username   the username.
    * @param   password   the password.
    * @exception   AxisFault   if something goes wrong with Axis.
    * @exception   ServiceException   if something goes wrong with Axis.
    */
   @SuppressWarnings("hiding")
   public SpineClient(URL url, String username, String password) throws AxisFault, ServiceException {
      SpineServiceLocator locator = new SpineServiceLocator();
      SpineSoapBindingStub bindings = (SpineSoapBindingStub) locator.getspine(url);
      bindings.setUsername(username);
      bindings.setPassword(password);
      this.setBindings(bindings);
      }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Changes the Axis bindings.
    * @param   bindings   the new bindings.
    */
   protected void setBindings(SpineSoapBindingStub bindings) {
      this.bindings = bindings;
      }

   /**
    * Gets the Axis bindings.
    * @return   the bindings.
    */
   protected SpineSoapBindingStub getBindings() {
      return this.bindings;
      }

   /**
    * {@inheritDocs}
    */
   @Override
   protected Stub getBindingsStub() {
      Stub stub = this.getBindings();
      return stub;
      }


//---------------------------
// Delegate methods
//---------------------------

   /**
    * Gets the time interval data is available for. Boundary dates are expected
    * to be from the moment of the request to thirty days in the future.
    * @return   the boundary dates.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public BoundaryDate getBoundaryDate() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      BoundaryDate boundaries = bindings.getBoundaryDate();
      return boundaries;
      }

   /**
    * Gets the depth interval data is available for. Data is always for the
    * surface and the depth interval is always {@code [0.0, 0.0]}.
    * @return   the boundary depths.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public BoundaryDepth getBoundaryDepth() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      BoundaryDepth boundaries = bindings.getBoundaryDepth();
      return boundaries;
      }

   /**
    * Gets the spatial interval data is available for. Data is available along
    * the center of the St. Lawrence River channel between Montréal and
    * Saint-Joseph-de-la-Rive, roughly between
    * <a href="http://goo.gl/maps/BnqR9">45.5 N 73.5 W</a> and
    * <a href="http://goo.gl/maps/3IMmE">47.4 N 70.4 W</a>.
    * @return   the boundary coordinates.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public BoundarySpatial getBoundarySpatial() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      BoundarySpatial boundaries = bindings.getBoundarySpatial();
      return boundaries;
      }

   /**
    * Gets the list of data types and their description. The SPINE service has
    * a single data type {@code spine}.
    * @return   the list of data types and their descriptions.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public Metadata[] getDataInfo() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      Metadata[] info = bindings.getDataInfo();
      return info;
      }

   /**
    * Gets the description of the SPINE service.
    * @return   the description.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public String getInfo() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      String info = bindings.getInfo();
      return info;
      }

   /**
    * Gets meta data about the SPINE service.
    * @return   the list of meta data and their value.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public Metadata[] getMetadata() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      Metadata[] info = bindings.getMetadata();
      return info;
      }

   /**
    * Gets the description of the various meta data.
    * @return   the list of meta data and their description.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public Metadata[] getMetadataInfo() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      Metadata[] info = bindings.getMetadataInfo();
      return info;
      }

   /**
    * Gets the name of the SPINE service.
    * @return   the name.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public String getName() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      String name = bindings.getName();
      return name;
      }

   /**
    * Gets the status of the service.
    * @return   the status.
    * @throws   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public Status getStatus() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      Status status = bindings.getStatus();
      return status;
      }

   /**
    * Gets the version of the SPINE service.
    * @return   the version.
    * @exception   RemoteException   if anything goes wrong.
    */
   @Override
   @SuppressWarnings("hiding")
   public String getVersion() throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      String version = bindings.getVersion();
      return version;
      }

   /**
    * Gets the forecasted water level at the given spatial and time coordinates.
    * <p>
    * Spatial coordinates latitude and longitude are given in decimal degrees
    * with positive latitude being northern and negative latitudes being
    * southern and with positive longitude being eastern and negative longitude
    * being western.
    * <p>
    * Time coordinates are the number of milliseconds from January 1st 1970 UTC.
    * <p>
    * The forecast value is given for the closest point along the center of the
    * channel and thus the geographical coordinates might not match.
    *
    * @param latitude   the latitude.
    * @param longitude   the longitude.
    * @param instant   the instants.
    * @return   the forecast data for the coordinates.
    * @exception   RemoteException   if anything goes wrong.
    */
   public ResultSpine interpolate(double latitude,
                                  double longitude,
                                  long instant) throws RemoteException {
      double[] latitudes = new double[] { latitude, };
      double[] longitudes = new double[] { longitude, };
      long[] instants = new long[] { instant, };
      ResultSpine results = this.interpolate(latitudes, longitudes, instants);
      return results;
      }

   /**
    * Gets the forecasted water level at the given spatial and time coordinate.
    * <p>
    * Spatial coordinates latitude and longitude are given in decimal degrees
    * with a positive latitude being northern and a negative latitudes being
    * southern and with a positive longitude being eastern and negative
    * longitude being western.
    * <p>
    * Time coordinates are in the format {@code yyyy-MM-dd HH:mm:ss} in UTC.
    * <p>
    * The forecast value is given for the closest point along the center of the
    * channel and thus the geographical coordinates might not match.
    *
    * @param latitude   the latitude.
    * @param longitude   the longitude.
    * @param date   the date.
    * @return   the forecast data for the coordinate.
    * @exception   RemoteException   if anything goes wrong.
    */
   public ResultSpine interpolate(double latitude,
                                  double longitude,
                                  String date) throws RemoteException {
      double[] latitudes = new double[] { latitude, };
      double[] longitudes = new double[] { longitude, };
      String[] dates = new String[] { date, };
      ResultSpine results = this.interpolate(latitudes, longitudes, dates);
      return results;
      }

   /**
    * Gets the forecasted water level at the given spatial and time coordinates.
    * <p>
    * Spatial coordinates latitude and longitude are given in decimal degrees
    * with positive latitudes being northern and negative latitudes being
    * southern and with positive longitudes being eastern and negative
    * longitudes being western.
    * <p>
    * Time coordinates are the number of milliseconds from January 1st 1970 UTC.
    * <p>
    * Values at the same index in the arrays represent a set of coordinates and
    * arrays must have the same length.
    * <p>
    * Forecast values are given for the closest point along the center of the
    * channel and thus the geographical coordinates might not match.
    *
    * @param latitudes   the list of latitudes.
    * @param longitudes   the list of longitudes.
    * @param instants   the list of instants.
    * @return   the list of forecast data for each coordinates.
    * @exception   RemoteException   if anything goes wrong.
    */
   public ResultSpine interpolate(double[] latitudes,
                                  double[] longitudes,
                                  long[] instants) throws RemoteException {
      String[] dates = this.format(instants);
      ResultSpine results = this.interpolate(latitudes, longitudes, dates);
      return results;
      }

   /**
    * Gets the forecasted water level at the given spatial and time coordinates.
    * <p>
    * Spatial coordinates latitudes and longitudes are given in decimal degrees
    * with positive latitudes being northern and negative latitudes being
    * southern and with positive longitudes being eastern and negative
    * longitudes being western.
    * <p>
    * Time coordinates are in the format {@code yyyy-MM-dd HH:mm:ss} in UTC.
    * <p>
    * Values at the same index in the arrays represent a set of coordinates and
    * arrays must have the same length.
    * <p>
    * Forecast values are given for the closest point along the center of the
    * channel and thus the geographical coordinates might not match.
    *
    * @param latitudes   the list of latitudes.
    * @param longitudes   the list of longitudes.
    * @param dates   the list of dates.
    * @return   the list of forecast data for each coordinates.
    * @exception   RemoteException   if anything goes wrong.
    */
   @SuppressWarnings("hiding")
   public ResultSpine interpolate(double[] latitudes,
                                  double[] longitudes,
                                  String[] dates) throws RemoteException {
      SpineSoapBindingStub bindings = this.getBindings();
      ResultSpine results = bindings.interpolate(latitudes, longitudes, dates);
      return results;
      }

   /**
    * Gets water level forecast.
    * @param   data   ignored.
    * @param   min_lat   ignored.
    * @param   max_lat   ignored.
    * @param   min_long   ignored.
    * @param   max_long   ignored.
    * @param   min_depth   ignored.
    * @param   max_depth   ignored.
    * @param   min_date   ignored.
    * @param   max_date   ignored.
    * @param   start   ignored.
    * @param   size   ignored.
    * @param   metadata   ignored.
    * @param   metadataSelection  ignored .
    * @param   order   ignored.
    * @return   nothing.
    * @exception   UnsupportedOperationException   always
    * @deprecated   Use {@link #interpolate(double[], double[], long[])}
    *               instead.
    */
   @Override
   @Deprecated
   public ResultSet search(String data,
                           double min_lat, double max_lat,
                           double min_long, double max_long,
                           double min_depth, double max_depth,
                           String min_date, String max_date,
                           int start, int size,
                           boolean metadata, String metadataSelection,
                           SortOrder order) {
      String message = getLocalizedString(USE_INTERPOLATE_MESSAGE);
      throw new UnsupportedOperationException(message);
      }


//---------------------------
// i18n methods
//---------------------------

   /** The key for the message of the exception telling to use {@link #interpolate(double[], double[], long[])}. */
   protected static final String USE_INTERPOLATE_MESSAGE = "use.interpolate.message"; //$NON-NLS-1$

   /**
    * Gets the resource bundle itself.
    * @return   the resource bundle.
    */
   protected static ResourceBundle getResourceBundle() {
      String name = SpineClient.class.getName();
      ResourceBundle bundle = ResourceBundle.getBundle(name, CharsetPropertiesControl.UTF_8_CONTROL);
      return bundle;
      }

   /**
    * Gets and formats the specified localized string from the menu resource
    * bundle.
    * @param   key   the key.
    * @param   arguments   the arguments to format the string.
    * @return   the value.
    */
   protected static String getLocalizedString(String key, Object... arguments) {
      ResourceBundle bundle = getResourceBundle();
      String pattern = bundle.getString(key);
      String message = MessageFormat.format(pattern, arguments);
      return message;
      }

   }
